﻿ADSStreams 0.7.8 x64
Technische Dokumentation und Implementierungsdetails
=====================================================

Stand: Version 0.7.8
Zielplattform: Windows 10 / Windows 11, x64
Assembler: Flat Assembler (FASM)
Dateityp: native 64-Bit-Windows-DLL / Explorer Property Sheet Handler

Diese Dokumentation beschreibt den aktuellen technischen Aufbau von ADSStreams 0.7.8.
Sie ist als Entwickler- und Architekturreferenz gedacht und konzentriert sich auf den
gegenwärtigen Programmzustand, die internen Datenflüsse, Sicherheitsentscheidungen,
COM-Mechanik und einige interessante Implementierungsdetails.


1. Zweck des Projekts
---------------------

ADSStreams ist eine native Windows-Explorer-Shell-Erweiterung zum Anzeigen und
Verwalten von NTFS Alternate Data Streams (ADS).

Nach der Registrierung erscheint bei den Eigenschaften einer einzelnen Datei ein
zusätzlicher Reiter "Streams". Dieser Reiter kann:

- benannte NTFS-$DATA-Streams auflisten,
- ihre logische Größe anzeigen,
- Text- oder Binärinhalt in einer begrenzten Vorschau darstellen,
- Streams als normale Dateien exportieren,
- normale Dateien als neue ADS importieren,
- einen ausgewählten benannten Stream nach Bestätigung löschen,
- die Anzeige neu einlesen,
- den direkten ADS-Pfad an externe Programme übergeben,
- einen MZ-basierten direkten Ausführungstest mit deutlicher Warnung durchführen,
- den Unterschied zwischen normalem Dateidatenstrom und benannten ADS größenmäßig
  darstellen.

Die DLL läuft direkt im Prozess von explorer.exe. Stabilität, begrenzte Speicher-
verwendung und eine möglichst enge Eingrenzung schreibender/destruktiver Operationen
sind deshalb zentrale Designziele.


2. Build und Binärformat
------------------------

Der Quelltext verwendet:

    format PE64 DLL GUI 6.0
    entry DllEntryPoint
    include 'win64ax.inc'

Damit entsteht eine native PE32+-DLL für x64-Windows.

Wichtig:

- Die DLL muss mit einem 64-Bit-FASM-/Win64-Include-Setup gebaut werden.
- Der 64-Bit-Explorer kann nur eine 64-Bit-In-Process-Shell-Erweiterung laden.
- Es gibt keine Abhängigkeit von einer C-Laufzeitbibliothek.
- Speicherverwaltung, Zeichenkettenverarbeitung, COM-VTables und Win32-Strukturen
  werden direkt in Assembler umgesetzt.
- Die DLL importiert ausschließlich Windows-Systembibliotheken.

Verwendete System-DLLs:

- KERNEL32.DLL
- ADVAPI32.DLL
- USER32.DLL
- GDI32.DLL
- SHELL32.DLL
- OLE32.DLL
- COMCTL32.DLL
- COMDLG32.DLL


3. Exportierte DLL-Funktionen
-----------------------------

ADSStreams 0.7.8 exportiert die vier klassischen COM-/regsvr32-Einstiegspunkte:

    DllGetClassObject
    DllCanUnloadNow
    DllRegisterServer
    DllUnregisterServer

Damit kann die DLL direkt mit regsvr32 registriert und wieder abgemeldet werden.

Beispiel für die 64-Bit-Version von regsvr32:

    %SystemRoot%\System32\regsvr32.exe ADSStreams.dll

Abmelden:

    %SystemRoot%\System32\regsvr32.exe /u ADSStreams.dll

Still/silent:

    %SystemRoot%\System32\regsvr32.exe /s ADSStreams.dll

    %SystemRoot%\System32\regsvr32.exe /s /u ADSStreams.dll


4. Selbstregistrierung und Registry-Aufbau
------------------------------------------

Die Selbstregistrierung ist absichtlich eine Registrierung pro Benutzer und benötigt
keine Administratorrechte.

Verwendeter Bereich:

    HKEY_CURRENT_USER\Software\Classes

CLSID von ADSStreams:

    {7B79E4E8-0CB2-4D92-89AE-84FA2A75EA71}

DllRegisterServer erzeugt im Wesentlichen:

    HKCU\Software\Classes\CLSID\
        {7B79E4E8-0CB2-4D92-89AE-84FA2A75EA71}

Default:
    ADS Streams Property Sheet

Darunter:

    ...\InprocServer32

Default:
    <vollständiger Pfad zur aktuell geladenen ADSStreams.dll>

ThreadingModel:
    Apartment

Property-Sheet-Handler:

    HKCU\Software\Classes\*\shellex\PropertySheetHandlers\ADSStreams

Default:
    {7B79E4E8-0CB2-4D92-89AE-84FA2A75EA71}

Der DLL-Pfad wird nicht hartcodiert. DllRegisterServer fragt ihn mit
GetModuleFileNameW von der bereits geladenen DLL ab.

Registrierungsfehler werden als HRESULT zurückgegeben. Schlägt eine Teilregistrierung
fehl, ruft DllRegisterServer DllUnregisterServer auf, um nur die eigenen bereits
angelegten Schlüssel wieder zu entfernen.

DllUnregisterServer löscht ausschließlich:

- den ADSStreams-PropertySheetHandlers-Schlüssel,
- den eigenen CLSID-Baum.

Nicht vorhandene Schlüssel gelten beim Abmelden als Erfolg. Dadurch ist ein wiederholtes

    regsvr32 /u ADSStreams.dll

harmlos.


5. DLL-Lebenszyklus
-------------------

DllEntryPoint behandelt nur DLL_PROCESS_ATTACH.

Beim Laden:

1. Das HINSTANCE wird in g_hInstance gespeichert.
2. DisableThreadLibraryCalls wird aufgerufen.
3. Für Thread-Attach/Detach entstehen dadurch keine unnötigen DllMain-Aufrufe.

Globale Zähler:

    g_objCount
    g_lockCount
    g_factoryRefs

DllCanUnloadNow liefert nur S_OK, wenn alle drei Zähler null sind. Andernfalls wird
S_FALSE zurückgegeben.

Damit berücksichtigt die DLL:

- lebende Shell-Extension-Objekte,
- LockServer-Referenzen,
- Referenzen auf die Class Factory.


6. COM-Architektur
------------------

ADSStreams ist ein klassischer In-Process-COM-Server.

Unterstützte Interfaces:

- IUnknown
- IClassFactory
- IShellExtInit
- IShellPropSheetExt

Es wird bewusst nur eine kleine COM-Oberfläche implementiert.

IShellPropSheetExt::ReplacePage ist nicht implementiert und gibt E_NOTIMPL zurück.


6.1 Class Factory
-----------------

DllGetClassObject prüft:

- ob die angeforderte CLSID der ADSStreams-CLSID entspricht,
- ob IUnknown oder IClassFactory angefordert wurde.

Bei Erfolg wird die statische FactoryObject-Instanz zurückgegeben.

Factory_CreateInstance:

- lehnt COM-Aggregation mit CLASS_E_NOAGGREGATION ab,
- allokiert das eigentliche Extension-Objekt vom Process Heap,
- initialisiert beide VTable-Zeiger,
- setzt den Referenzzähler auf 1,
- erhöht g_objCount,
- liefert den passenden Interface-Zeiger zurück.

Unsupported Interfaces führen zu E_NOINTERFACE und das frisch erzeugte Objekt wird
sofort wieder freigegeben.


6.2 Zwei Interfaces in einer einzigen Objektallokation
------------------------------------------------------

Das Extension-Objekt besitzt zwei VTable-Zeiger in derselben Speicherallokation:

    Offset  0: IShellExtInit / IUnknown VTable
    Offset  8: IShellPropSheetExt VTable

Objektlayout:

    OBJ_SI_VTBL          = 0
    OBJ_PS_VTBL          = 8
    OBJ_REFS             = 16
    OBJ_PATH             = 24
    OBJ_GUI_FONT         = 32
    OBJ_HEX_FONT         = 40
    OBJ_SHEET_HWND       = 48
    OBJ_EXPAND_DELTA     = 56
    OBJ_EXPANDED         = 60
    OBJ_ORIG_SHEET_RECT  = 64
    OBJ_SIZE             = 80

Das IShellPropSheetExt-Interface zeigt also auf base+8.

Die Wrapper für dieses Interface subtrahieren 8 Byte von RCX, bevor sie auf die
gemeinsame Objektbasis zugreifen.

Das ist ein kompakter manueller Ersatz für das, was ein C++-Compiler bei mehrfachen
COM-Interfaces normalerweise automatisch erzeugen würde.


6.3 QueryInterface und Referenzzählung
--------------------------------------

IUnknown und IShellExtInit verwenden den Basiszeiger.

IShellPropSheetExt verwendet base+8.

QueryInterface erhöht OBJ_REFS atomar mit LOCK INC.

Release verwendet LOCK DEC. Beim Übergang auf null:

- wird der selbst erzeugte Hex-Font gelöscht,
- der gespeicherte Dateipfad freigegeben,
- das Objekt selbst freigegeben,
- g_objCount verringert.

Der normale Explorer-GUI-Font ist nur ausgeliehen und wird ausdrücklich nicht gelöscht.


7. Übernahme der ausgewählten Explorer-Datei
--------------------------------------------

IShellExtInit::Initialize verarbeitet IDataObject.

Verwendetes Clipboard-/Shell-Format:

    CF_HDROP
    TYMED_HGLOBAL

Ablauf:

1. FORMATETC für CF_HDROP wird aufgebaut.
2. IDataObject::GetData wird direkt über den COM-VTable-Slot aufgerufen.
3. DragQueryFileW(..., -1, ...) ermittelt die Anzahl ausgewählter Dateien.
4. Nur genau eine Datei wird akzeptiert.
5. Die notwendige Pfadlänge wird mit DragQueryFileW ermittelt.
6. Der UTF-16-Pfad wird dynamisch vom Process Heap allokiert.
7. DragQueryFileW kopiert den Pfad in OBJ_PATH.
8. ReleaseStgMedium gibt das Shell-Medium frei.

Bei Mehrfachauswahl wird kein Streams-Reiter hinzugefügt.

Das ist eine bewusste Vereinfachung: Die gesamte Oberfläche und alle Aktionen arbeiten
immer mit genau einer Besitzerdatei.


8. Erzeugen des Property-Sheet-Reiters
--------------------------------------

IShellPropSheetExt::AddPages verwendet keinen klassischen Ressourcen-Dialog.

Stattdessen gibt es ein minimales DLGTEMPLATE im Datensegment:

- WS_CHILD
- WS_TABSTOP
- DS_CONTROL
- DS_3DLOOK
- keine vordefinierten Child Controls

AddPages baut eine PROPSHEETPAGEW-Struktur auf und verwendet:

    PSP_DLGINDIRECT
    PSP_USETITLE
    PSP_USECALLBACK

Titel:

    Streams

Der Dialoginhalt wird später in PageDlgProc dynamisch mit CreateWindowExW erzeugt.

Vorteile dieser Architektur:

- kein separates .rc/.res-File notwendig,
- Layout und Control-Stile bleiben komplett im Assemblerquelltext,
- Fonts können unmittelbar nach der Erzeugung an jedes Control weitergereicht werden,
- das Projekt bleibt eine einzelne FASM-Quelle plus Hilfsdateien.


8.1 Lebensdauer der Property Page
---------------------------------

Vor CreatePropertySheetPageW erhöht AddPages den Objekt-Referenzzähler.

Der PROPSHEETPAGE-Callback behandelt PSPCB_RELEASE und ruft dann Obj_Release_Base auf.

Dadurch bleibt das COM-Objekt auch dann gültig, wenn Explorer die Page erzeugt,
vorübergehend hält oder nie aktiviert.

Lehnt die Shell die Seite ab, wird DestroyPropertySheetPage aufgerufen. Auch dann
läuft der Release-Callback und balanciert die Referenz.


9. Aufbau der Streams-Oberfläche
--------------------------------

PageDlgProc erzeugt dynamisch:

- Label für ausgewählte Datei und Größenaufteilung,
- Read-only-Pfadfeld,
- Label "Alternate data streams:",
- ListView mit Streams,
- Aktionsbuttons Add / Delete / Refresh / Export,
- Read-only-Vorschaufenster,
- Statuszeile,
- "Tips by api0cradle",
- About,
- Versionsmarker.

Die ListView verwendet:

- Report-Modus,
- Single Selection,
- Full Row Select,
- Double Buffering,
- InfoTip-Unterstützung.


10. Streamdarstellung: sichtbarer Name vs. kanonischer Name
-----------------------------------------------------------

FindFirstStreamW liefert Namen in der Form:

    :secret.txt:$DATA

Die Oberfläche zeigt dagegen nur:

    secret.txt

Dazu besitzt die ListView drei Spalten/Subitems:

    Spalte 0: sichtbarer Streamname
    Spalte 1: Größe in Byte
    Spalte 2: unsichtbar, Breite 0

Die versteckte dritte Spalte enthält weiterhin exakt den kanonischen NTFS-Namen:

    :secret.txt:$DATA

Alle sicherheitsrelevanten und technischen Aktionen verwenden diese versteckte
kanonische Form, nicht den sichtbaren Text.

Das betrifft insbesondere:

- Preview,
- Export,
- Delete,
- Shift+Doppelklick,
- Hover-InfoTip.

Dadurch kann die UI benutzerfreundlich aussehen, ohne intern Streamnamen erraten oder
zurückkonstruieren zu müssen.


11. ADS-Enumeration
-------------------

PopulateStreamsList verwendet:

    FindFirstStreamW
    FindNextStreamW
    FindClose

Informationsklasse:

    FindStreamInfoStandard

WIN32_FIND_STREAM_DATA enthält:

- 64-Bit-Streamgröße,
- kanonischen Streamnamen.

Der unbenannte Standarddatenstrom:

    ::$DATA

wird bewusst nicht als eigener Listeneintrag angezeigt.

Für jeden benannten Stream:

1. kanonischen Namen lesen,
2. ::$DATA ausfiltern,
3. sichtbaren Namen zwischen führendem ":" und finalem ":$DATA" anzeigen,
4. 64-Bit-Größe in Dezimaltext umwandeln,
5. kanonischen Namen in Hidden Subitem 2 speichern.

Die Größenanzeige in der ListView ist die logische Streamlänge in Byte.


12. Hover-InfoTip
-----------------

Die ListView nutzt LVS_EX_INFOTIP.

Bei LVN_GETINFOTIPW erzeugt FillStreamInfoTip die kompakte technische Identität.

Aus:

    C:\Ordner\owner.txt
    :secret.txt:$DATA

wird im Hover:

    owner.txt:secret.txt:$DATA

Der Verzeichnispfad wird absichtlich weggelassen.

Der Tooltip ist damit technisch präziser als die normale Listenanzeige, bleibt aber
kompakt.


13. Größenaufteilung Hauptdaten + ADS
-------------------------------------

Die obere Zeile zeigt beispielsweise:

    Selected file (~191 KiB + ~297 MiB ADS):

GetStreamSizeBreakdown enumeriert alle $DATA-Streams derselben Datei.

Dabei wird:

- ::$DATA separat als normale Dateidatenmenge gespeichert,
- die Größe aller benannten Streams zu einem ADS-Gesamtwert addiert.

Wichtig:

Diese Werte sind logische Datenlängen, keine physisch belegten NTFS-Cluster.

Kompression, Sparse-Dateien, Resident Data und Cluster-Rundung können deshalb dazu
führen, dass Windows "Größe auf Datenträger" einen anderen Wert zeigt.

FormatApproxBytesW verwendet Binäreinheiten:

- B
- KiB
- MiB
- GiB
- TiB

Ab 1 KiB wird auf ganze Einheiten gerundet und ein "~" vorangestellt.


14. Vorschau: 64-KiB-Grenze
----------------------------

PREVIEW_MAX:

    65536 Byte

Zum Lesen werden tatsächlich:

    65537 Byte

angefordert.

Das zusätzliche Byte dient ausschließlich zur Erkennung, ob der Stream größer als
die Vorschaugrenze ist.

Ablauf:

1. ausgewählten kanonischen Namen aus Hidden Subitem 2 lesen,
2. vollständigen ADS-Pfad bilden,
3. CreateFileW nur mit GENERIC_READ öffnen,
4. 65537 Byte festen Speicher allokieren,
5. ReadFile ausführen,
6. bei >65536 Byte die Darstellung auf 65536 begrenzen und "truncated" melden.

Die Vorschaugröße wird niemals aus der tatsächlichen Streamgröße abgeleitet.
Dadurch kann ein sehr großer Stream nicht automatisch zu einer entsprechend großen
Heap-Allokation im Explorer führen.


15. Text-/Binärerkennung
------------------------

Die Vorschau versucht zuerst eine Textdarstellung.

IsLikelyText behandelt sofort als Text:

- UTF-16LE mit BOM FF FE,
- UTF-16BE mit BOM FE FF,
- UTF-8 mit BOM EF BB BF.

Bei Daten ohne BOM wird eine Heuristik verwendet.

Ein eingebettetes NUL-Byte spricht stark für Binärdaten.

Erlaubte Steuerzeichen sind unter anderem:

- TAB
- LF
- FF
- CR

Andere problematische Steuerzeichen werden gezählt.

Die Toleranz liegt ungefähr bei drei Prozent:

    bad_controls <= floor(byte_count / 32) + 1

Ist der Inhalt textähnlich, versucht BuildTextPreview die Konvertierung.


15.1 Unterstützte Textkodierungen
---------------------------------

BuildTextPreview unterstützt:

- UTF-16LE mit BOM,
- UTF-16BE mit BOM,
- UTF-8 mit BOM,
- UTF-8 ohne BOM,
- ANSI-Fallback über CP_ACP.

Bei generischem Text wird zuerst striktes UTF-8 mit MB_ERR_INVALID_CHARS versucht.

Scheitert das, folgt der aktuelle Windows-ANSI-Codepage-Fallback.

Bei einer UTF-8-BOM wird zuerst streng dekodiert; bei einzelnen fehlerhaften Bytes
gibt es anschließend noch einen permissiven UTF-8-Versuch.


16. Hexdump
-----------

Wenn BuildTextPreview keinen Text liefert, erzeugt BuildHexPreview einen klassischen
Hex-/ASCII-Dump.

Eigenschaften:

- 16 Byte pro Zeile,
- 8-stellige hexadezimale Offsetadresse,
- optische Trennung nach 8 Byte,
- ASCII-Spalte rechts,
- Bytes außerhalb 0x20 bis 0x7E werden als "." dargestellt.

Beispielprinzip:

    00000000  4D 5A 90 00 03 00 00 00  04 00 00 00 FF FF 00 00   MZ..............

Für den Hexdump wird ein Heap-Puffer anhand der bereits auf 64 KiB begrenzten
Vorschaugröße allokiert.

Das Preview-Edit-Control erhält ein erhöhtes Textlimit von 1.000.000 Zeichen, weil
eine Hexdarstellung deutlich größer als ihre Rohdaten ist.


17. Font-Strategie
------------------

ADSStreams versucht, optisch wie ein normaler Explorer-Eigenschaftsreiter zu wirken.

InitPageFonts:

1. fragt den Font des übergeordneten Property Sheets ab,
2. falls nötig den Font der eigenen Page,
3. sonst DEFAULT_GUI_FONT.

Dieser GUI-Font wird ausgeliehen und in OBJ_GUI_FONT gespeichert.

Für Binärvorschauen wird zusätzlich ein eigener Monospace-Font erzeugt:

- Grundlage sind die Metriken des Explorer-Fonts,
- Gewicht FW_NORMAL,
- keine Kursiv-/Unterstreich-/Strikeout-Attribute,
- Face Name "Consolas",
- FIXED_PITCH + FF_MODERN.

So bleibt die Schriftgröße/DPI-Anpassung des Explorer-Fensters erhalten, während der
Hexdump eine ruhige Monospace-Ausrichtung erhält.

Der Consolas-HFONT gehört ADSStreams und wird bei der Objektfreigabe mit DeleteObject
zerstört.


18. Dynamische Breite des Eigenschaftenfensters
------------------------------------------------

Eine Besonderheit ist die dynamische Verbreiterung des gesamten Windows-
Eigenschaftenfensters, solange der Reiter "Streams" aktiv ist.

Ziel:

Eine vollständige 16-Byte-Hexzeile soll ohne horizontalen Scrollbedarf sichtbar sein.

MeasureHexPreviewOuterWidth:

1. holt den DC des Preview-Fensters,
2. wählt denselben Monospace-Font wie im Hexdump,
3. misst eine repräsentative komplette Hexzeile mit GetTextExtentPoint32W,
4. addiert Scrollbar-/Rahmenreserve,
5. verwendet mindestens 350 Pixel.

Bei PSN_SETACTIVE:

- ursprüngliches Property-Sheet-Rechteck speichern,
- benötigtes Delta berechnen,
- gesamtes Property Sheet verbreitern,
- Tab-Control verbreitern,
- Streams-Page verbreitern,
- Pfad, ListView, Preview und Status verbreitern,
- Versionsanzeige und Standardbuttons nach rechts verschieben.

Bei PSN_KILLACTIVE:

- alle von ADSStreams vorgenommenen Geometrieänderungen rückgängig machen,
- ursprüngliche Breite und Höhe des Property Sheets wiederherstellen.

Die ursprüngliche Bildschirmposition wird beim Restore nicht erzwungen. Dadurch wird
nur die von ADSStreams veränderte Größe zurückgesetzt.


19. Refresh
-----------

RefreshStreamsPage ist ausdrücklich eine reine Lese-/UI-Funktion.

Sie:

1. berechnet die Größenaufteilung neu,
2. löscht alle ListView-Zeilen,
3. enumeriert die Streams erneut,
4. setzt Preview und Font zurück,
5. aktualisiert die Statuszeile.

Sie führt selbst keine Datei-Schreiboperation und keinen Löschvorgang aus.

Add und Delete rufen nach erfolgreicher Änderung ebenfalls RefreshStreamsPage auf.


20. Export
----------

ExportSelectedStream kopiert einen ADS vollständig in eine normale Datei.

Der Export ist nicht durch PREVIEW_MAX begrenzt.

Kopierblock:

    65536 Byte

Sicherheitsentscheidungen:

- Quelle ausschließlich GENERIC_READ,
- Ziel wird mit CREATE_NEW angelegt,
- vorhandene Dateien werden niemals überschrieben,
- Export auf die Besitzerdatei selbst wird blockiert,
- der Transferpuffer wird vor dem Erzeugen der Zieldatei allokiert,
- ein fehlgeschlagener Export löscht eine teilweise neu angelegte Datei nicht
  automatisch.

Letzter Punkt ist bewusst konservativ:

Bei einem Schreib-/Lesefehler bleibt eine eventuell teilweise erzeugte neue Datei zur
Inspektion bestehen. Die Fehlerbehandlung schließt nur Handles und entfernt nicht
eigenmächtig Daten.


21. Add / Import
----------------

ImportFileAsStream importiert eine normale Datei als neuen benannten ADS.

Ablauf:

1. GetOpenFileNameW zeigt einen Dateiauswahldialog.
2. Der Basisdateiname wird extrahiert.
3. Daraus wird ein kanonischer Streamname erzeugt:

       C:\Temp\payload.bin
       -> :payload.bin:$DATA

4. Der kanonische Name wird erneut validiert.
5. Besitzerpfad + Streamname werden zu einem ADS-Pfad verbunden.
6. Transferpuffer von 64 KiB allokieren.
7. Quelldatei read-only öffnen.
8. Ziel-ADS mit CREATE_NEW erzeugen.
9. Inhalt blockweise übertragen.
10. Liste und Größenanzeige aktualisieren.

Ein vorhandener Stream gleichen Namens wird niemals überschrieben.

ERROR_FILE_EXISTS und ERROR_ALREADY_EXISTS werden ausdrücklich erkannt und führen zu
einer sichtbaren Meldung.

Auch beim Import werden fehlgeschlagene teilweise neu erzeugte Streams nicht
automatisch gelöscht. Falls das Ziel bereits angelegt wurde, wird es durch Refresh
sichtbar gemacht.


22. Streamnamen-Validierung
---------------------------

IsSafeNamedDataStreamName akzeptiert nur die Form:

    :name:$DATA

Zusätzliche Bedingungen:

- führendes ":" erforderlich,
- Name darf nicht leer sein,
- ::$DATA wird blockiert,
- "\" wird abgelehnt,
- "/" wird abgelehnt,
- "*" wird abgelehnt,
- "?" wird abgelehnt,
- exakt finales ":$DATA" erforderlich,
- nach "$DATA" darf nichts mehr folgen.

BuildCanonicalStreamNameFromPath prüft außerdem beim Erzeugen eines Streamnamens aus
einem normalen Dateinamen die gleichen wesentlichen problematischen Zeichen.

Dadurch arbeiten schreibende/destruktive Funktionen nicht mit frei zusammengesetzten
Benutzereingaben, sondern mit einer engen kanonischen Syntax.


23. Delete
----------

DeleteSelectedStream ist der einzige Pfad, der DeleteFileW auf einen Dateidatenpfad
anwendet.

Ablauf:

1. aktuell selektierte ListView-Zeile bestimmen,
2. kanonischen Namen aus Hidden Subitem 2 lesen,
3. IsSafeNamedDataStreamName aufrufen,
4. Besitzerpfad + kanonischen Namen verbinden,
5. Warnung mit Yes/No anzeigen,
6. "No" ist der Standardbutton,
7. nur bei "Yes" DeleteFileW auf genau diesen ADS-Pfad aufrufen,
8. anschließend Refresh.

Der unbenannte Hauptdatenstrom ::$DATA kann diese Validierung nicht passieren.

Es gibt:

- keine Wildcards,
- keine rekursive Löschung,
- keine Verzeichnislöschung,
- keine automatische Cleanup-Löschung,
- keine Löschung einer Exportdatei bei einem Fehler.


24. Direkter ADS-Kompatibilitätstest per Shift+Doppelklick
----------------------------------------------------------

Ein normaler Doppelklick führt keine Sonderaktion aus.

Nur:

    Shift + Doppelklick

aktiviert OpenWithSelectedStreamDirect.

Die Shift-Taste wird mit GetAsyncKeyState(VK_SHIFT) geprüft.

Aus dem internen kanonischen Namen:

    :video.mp4:$DATA

und der Besitzerdatei:

    C:\Test\host.png

wird zunächst:

    C:\Test\host.png:video.mp4:$DATA

Für die Übergabe an normale Programme wird nur das finale ":$DATA" abgeschnitten:

    C:\Test\host.png:video.mp4

Dieser Pfad ist der echte NTFS-ADS-Pfad.

Es wird dabei ausdrücklich:

- keine temporäre Datei erzeugt,
- nichts exportiert,
- kein automatischer Fallback verwendet.


25. Nicht-MZ-Streams: Handler-Popup
-----------------------------------

Für normale Streams wird aus dem Streamnamen nur zur Ermittlung der Dateiendung ein
"normal" aussehender Dateiname erzeugt. Es wird keine reale Datei angelegt.

Beispiel:

    :movie.mp4:$DATA -> movie.mp4 -> .mp4

SHAssocEnumHandlers enumeriert registrierte Anwendungen für diese Endung.

Maximal:

    32 Handler

Für jeden Handler werden über IAssocHandler unter anderem abgefragt:

- GetName: ausführbarer Programmpfad,
- GetUIName: freundlicher Anzeigename.

Die Namen erscheinen in einem Popup-Menü am Mauszeiger.

Zusätzlich:

    Browse for program...

Damit kann ein beliebiges klassisches EXE-Programm ausgewählt werden.

Beim Start erhält das Programm den direkten ADS-Pfad als ein einzelnes gequotetes
Argument.

Beispiel:

    "C:\Program Files\Player\Player.exe" "C:\Test\host.png:movie.mp4"

Dieser Mechanismus ist bewusst ein Kompatibilitätstest. Er bildet nicht zwangsläufig
die vollständige registrierte Shell-Commandline eines Programms mit speziellen
Schaltern nach.


26. MZ-Erkennung und direkter Ausführungstest
---------------------------------------------

Beim Shift+Doppelklick wird der ausgewählte Stream vor dem Handler-Popup read-only
geöffnet und es werden genau die ersten zwei Bytes geprüft.

Signatur:

    4D 5A
    "MZ"

Wenn MZ erkannt wird, wird der Stream als potentiell ausführbarer PE-artiger Inhalt
behandelt.

Wichtig:

MZ allein beweist nicht, dass der Stream eine gültige EXE ist. Auch DLLs oder andere
PE-Dateien beginnen mit MZ.

Vor jedem direkten Startversuch erscheint eine deutliche Warnmeldung.

Standardantwort:

    No

Nur nach ausdrücklichem Yes wird CreateProcessW direkt mit dem echten ADS-Pfad
aufgerufen.

Es gibt auch hier:

- keinen Export,
- keine Tempdatei,
- keinen Fallback.

Schlägt CreateProcessW fehl, wird nur eine Fehlermeldung angezeigt.

Damit lässt sich experimentell feststellen, ob Windows den konkreten Stream direkt als
Prozessimage akzeptiert.


27. Zeichenketten- und Heap-Hilfsfunktionen
-------------------------------------------

Der Quelltext implementiert mehrere kleine Hilfsroutinen selbst:

GuidEqual
    Vergleich zweier 128-Bit-GUIDs.

WStrEqual
    Vergleich zweier UTF-16-NUL-terminierter Strings.

WStrLen
    UTF-16-Zeichenlänge.

WStrCopyEnd
    Kopiert einen UTF-16-String und gibt die Adresse des Ziel-NUL zurück.
    Praktisch für String-Konkatenation.

U64ToDecW
    Konvertiert unsigned 64-Bit-Zahlen ohne CRT in UTF-16-Dezimaltext.

HResultFromWin32
    Baut HRESULT_FROM_WIN32-kompatible HRESULTs.

FreeHeapPtr
    Freigabe eines Process-Heap-Puffers, NULL-sicher.

BuildStreamPath
    Verkettet Besitzerpfad und kanonischen Streamnamen.

BuildDirectADSPath
    Erzeugt den direkten owner:stream-Pfad ohne finales ":$DATA".

BuildQuotedSingleArg
    Erzeugt ein gequotetes einzelnes Kommandozeilenargument.

BuildExportFileName
    Erzeugt aus :name:$DATA einen normalen Dateinamensvorschlag.


28. Speicherverwaltung
----------------------

Dynamische Speicherbereiche werden überwiegend über:

    GetProcessHeap
    HeapAlloc
    HeapFree

verwaltet.

Typische Allokationen:

- gespeicherter Besitzerpfad,
- ADS-Pfad,
- direkte ADS-Pfade,
- Dateidialog-Puffer,
- 64-KiB-Transferpuffer,
- Textvorschau,
- Hexvorschau,
- gequotete Kommandozeilenargumente.

COM-Strings von IAssocHandler werden dagegen mit CoTaskMemFree freigegeben, weil diese
von COM entsprechend allokiert zurückgegeben werden.

Der Code trennt diese Besitzmodelle bewusst.


29. Handle- und Ressourcenverwaltung
------------------------------------

Dateihandles werden mit CloseHandle geschlossen.

Stream-Enumeration:

    FindClose

Property Pages:

    DestroyPropertySheetPage bei Ablehnung durch die Shell.

GDI:

- GUI-Font ausgeliehen,
- eigener Hex-Font via DeleteObject.

Popup-Menüs:

    DestroyMenu

COM-Handler und Enumerator:

    IUnknown::Release

STGMEDIUM:

    ReleaseStgMedium

Die direkten Ausführungstests schließen hProcess und hThread unmittelbar nach
erfolgreichem CreateProcessW; der gestartete Prozess läuft dadurch normal weiter.


30. Win64-ABI und Stackdisziplin
-------------------------------

Da der Code vollständig in x64-Assembler geschrieben ist, muss jede WinAPI- und
COM-Funktion die Microsoft-x64-Calling-Convention exakt einhalten.

Wesentliche Punkte:

- RCX, RDX, R8, R9 für die ersten vier Argumente,
- weitere Argumente auf dem Stack,
- 32 Byte Shadow Space für Calls,
- 16-Byte-Stackausrichtung an Call-Grenzen,
- nichtflüchtige Register werden bei Bedarf gesichert und restauriert.

Das ist bei einer In-Process-Explorer-DLL besonders wichtig: Ein ABI-Fehler betrifft
nicht nur ein separates Hilfsprogramm, sondern kann explorer.exe selbst zum Absturz
bringen.

Viele Routinen reservieren deshalb bewusst etwas mehr Stack-Speicher für:

- Shadow Space,
- Win32-Strukturen,
- temporäre UTF-16-Puffer,
- Alignment.


31. Wichtige Win32-Strukturen werden manuell aufgebaut
------------------------------------------------------

ADSStreams nutzt keine C-Header zur Laufzeit.

Mehrere Strukturen werden direkt anhand ihrer x64-Offsets auf dem Stack aufgebaut,
unter anderem:

- FORMATETC
- STGMEDIUM
- PROPSHEETPAGEW
- LVITEMW
- LVCOLUMNW
- OPENFILENAMEW
- STARTUPINFOW
- PROCESS_INFORMATION
- WIN32_FIND_STREAM_DATA

Beispiel PROPSHEETPAGEW:

Die x64-Größe wird mit 104 Byte angesetzt, und die Feld-Offsets sind im Quelltext
explizit definiert.

Das macht die Implementierung sehr transparent, verlangt aber strikte Beachtung der
64-Bit-Strukturlayouts.


32. Warum die DLL bewusst keine großen Preview-Allokationen macht
-----------------------------------------------------------------

Die Vorschaugrenze von 64 KiB ist keine NTFS-Grenze.

Sie ist ein Stabilitäts- und Responsiveness-Limit für eine DLL, die direkt in Explorer
läuft.

Ein Binärstream von 64 KiB wächst als UTF-16-Hexdump bereits auf mehrere hundert
Kilobyte Darstellungstext.

Sehr große Streams können trotzdem vollständig exportiert werden, weil Export und
Import blockweise mit einem konstanten 64-KiB-Puffer arbeiten.

Für tiefergehende Analysen großer Datenströme ist daher der vorgesehene Workflow:

    ADS exportieren -> externen Hexeditor / Spezialtool verwenden


33. Sicherheitsmodell
----------------------

Die wichtigsten Sicherheitsprinzipien der aktuellen Implementierung:

1. Vorschau ist read-only.
2. Exportquelle ist read-only.
3. Exportziel benutzt CREATE_NEW.
4. Add-Ziel benutzt CREATE_NEW.
5. Vorhandene Dateien/Streams werden nicht still überschrieben.
6. Transferpuffer werden vor dem Erzeugen neuer Ziele allokiert.
7. Fehlgeschlagene neue Ziele werden nicht automatisch gelöscht.
8. Delete akzeptiert nur validierte kanonische benannte $DATA-Streams.
9. ::$DATA ist beim Delete blockiert.
10. Delete erfordert eine explizite Yes-Bestätigung; No ist Standard.
11. Direkte ADS-Kompatibilitätstests erzeugen keine Tempdatei.
12. MZ-Inhalte erfordern vor CreateProcessW eine zusätzliche Warnung.
13. Refresh führt keine Schreib- oder Löschoperation aus.
14. Registry-Unregister löscht nur die eigenen ADSStreams-Schlüssel.


34. Bekannte Grenzen und bewusstes Verhalten
--------------------------------------------

- Nur eine ausgewählte Datei erhält den Reiter.
- Die Erweiterung ist x64.
- Die eigentliche ADS-Unterstützung setzt ein Dateisystem/Objekt voraus, für das die
  Windows-Stream-APIs entsprechende Streams liefern.
- ::$DATA wird nicht in der Streamliste angezeigt.
- Preview ist auf 64 KiB begrenzt.
- Größenanzeige bedeutet logische Streamlänge, nicht physische Clusterbelegung.
- Textklassifikation ist heuristisch.
- MZ-Erkennung prüft nur die ersten zwei Bytes und ist keine vollständige PE-Validierung.
- Direkte Übergabe an Drittprogramme testet owner:stream als normales Argument.
  Programme mit speziellen registrierten Shell-Commandlines können sich daher anders
  verhalten als beim regulären Öffnen einer normalen Datei.
- Manche Programme unterstützen ADS-Pfade direkt, andere nicht.
- Die DLL führt keine automatische Konvertierung oder Temp-Export-Lösung ein, wenn ein
  Drittprogramm den ADS-Pfad nicht akzeptiert.


35. Interessante Implementierungsdetails
----------------------------------------

A) Versteckte kanonische ListView-Spalte
   --------------------------------------
   Die UI zeigt einen einfachen Dateinamen, behält aber den exakten NTFS-Streamnamen
   unsichtbar im selben ListView-Datensatz. Dadurch sind UI und Sicherheitsidentität
   sauber getrennt.

B) 65537-Byte-Read für eine 65536-Byte-Vorschau
   ---------------------------------------------
   Ein einziges zusätzlich gelesenes Byte genügt, um "größer als Preview" festzustellen,
   ohne die tatsächliche Streamgröße für eine große Allokation verwenden zu müssen.

C) Dynamisches Messen statt fester Fensterbreite
   ----------------------------------------------
   Die benötigte Breite wird mit dem real verwendeten Monospace-Font gemessen. Dadurch
   hängt die Hexdarstellung weniger von DPI, Fontmetriken oder Windows-Skalierung ab.

D) Manuelle COM-Mehrfachinterfaces
   --------------------------------
   Zwei Interface-VTables liegen in derselben 80-Byte-Objektallokation. Das zweite
   Interface wird als base+8 exponiert. QueryInterface und Wrapper korrigieren den
   Zeiger selbst.

E) Keine CRT
   ---------
   Selbst einfache Aufgaben wie UTF-16-Länge, 64-Bit-Dezimalformatierung und
   Stringverkettung werden intern umgesetzt. Das hält die DLL-Abhängigkeiten auf reine
   Windows-Systembibliotheken beschränkt.

F) Trennung von logischer ADS-Größe und Explorer-"Size on disk"
   -------------------------------------------------------------
   Die Erweiterung zeigt bewusst die eigentliche Datenmenge von Hauptstream und ADS,
   statt den Wert der physischen NTFS-Allokation nachzuahmen.

G) Echter ADS-Pfad für Kompatibilitätstests
   ----------------------------------------
   Die Shift+Doppelklick-Funktion arbeitet nicht mit einer exportierten Kopie. Das
   getestete Programm bekommt tatsächlich owner:stream. Damit lässt sich reales
   ADS-Verhalten eines Programms beobachten.

H) MZ-Entscheidung nach Inhalt statt Dateiendung
   ----------------------------------------------
   Eine Datei namens "harmlos.txt" wird für den direkten Ausführungspfad trotzdem als
   potentiell ausführbar behandelt, wenn der Stream mit 4D 5A beginnt.

I) Registrierung ohne Administrator
   ---------------------------------
   Durch HKCU\Software\Classes lässt sich der Handler klassisch per regsvr32
   registrieren, ohne systemweite HKLM-Änderungen vorzunehmen.

J) Destruktiver Pfad möglichst schmal
   -----------------------------------
   DeleteFileW wird nur für den validierten, ausgewählten benannten ADS-Pfad benutzt.
   Import-/Export-Fehlerpfade verwenden keine automatische Datei-Löschung.


36. Grober Datenfluss
---------------------

Explorer
   |
   v
DllGetClassObject
   |
   v
IClassFactory::CreateInstance
   |
   v
IShellExtInit::Initialize
   |
   +--> CF_HDROP -> genau ein Dateipfad -> OBJ_PATH
   |
   v
IShellPropSheetExt::AddPages
   |
   v
CreatePropertySheetPageW
   |
   v
PageDlgProc / WM_INITDIALOG
   |
   +--> Controls dynamisch erzeugen
   +--> Streams enumerieren
   +--> Größe Hauptstream + ADS berechnen
   |
   v
Benutzeraktion
   |
   +--> Auswahl        -> PreviewSelectedStream
   +--> Add            -> ImportFileAsStream -> Refresh
   +--> Delete         -> Validierung -> Warnung -> DeleteFileW -> Refresh
   +--> Refresh        -> reine Neu-Enumeration
   +--> Export         -> blockweises Read/Write mit CREATE_NEW
   +--> Shift+DblClick -> echter ADS-Pfad -> Handler-Test / MZ-Warnpfad


37. Relevante Kernfunktionen im Quelltext
-----------------------------------------

DllEntryPoint
    DLL-Initialisierung.

DllGetClassObject
    COM-Class-Factory-Einstieg.

DllCanUnloadNow
    Lebensdauerentscheidung.

DllRegisterServer
    regsvr32-Selbstregistrierung.

DllUnregisterServer
    per-user Deinstallation der Registry-Einträge.

Factory_CreateInstance
    Erzeugt das Extension-Objekt.

ObjSI_Initialize
    Übernimmt die ausgewählte Explorer-Datei.

ObjPS_AddPages
    Fügt den "Streams"-Reiter hinzu.

PageCallbackProc
    Hält die COM-Lebensdauer der Property Page korrekt.

PageDlgProc
    UI-Erzeugung und Event-Routing.

PopulateStreamsList
    ADS-Enumeration und ListView-Befüllung.

FillStreamInfoTip
    Hover-Darstellung owner:name:$DATA.

GetStreamSizeBreakdown
    Hauptdaten-/ADS-Größenberechnung.

PreviewSelectedStream
    64-KiB-Read und Text-/Hex-Entscheidung.

BuildTextPreview
    UTF-16/UTF-8/ANSI-Konvertierung.

BuildHexPreview
    Hex-/ASCII-Darstellung.

ImportFileAsStream
    Datei -> neuer ADS.

ExportSelectedStream
    ADS -> neue normale Datei.

DeleteSelectedStream
    explizit bestätigtes Löschen eines benannten ADS.

RefreshStreamsPage
    Neu-Enumeration ohne Dateiänderung.

OpenWithSelectedStreamDirect
    Shift+Doppelklick, Handlerauswahl und MZ-Ausführungstest.

MeasureHexPreviewOuterWidth
    DPI-/fontabhängige Breitenberechnung.

ExpandStreamsSheetForHex
    Verbreitert das gesamte Property Sheet.

RestoreStreamsSheetWidth
    Stellt die normale Windows-Breite wieder her.


38. Praktische Entwicklerhinweise
---------------------------------

Nach einem Neubau einer bereits geladenen Shell-Erweiterung kann explorer.exe die alte
DLL noch im Prozess halten. Für Tests ist deshalb gegebenenfalls ein Explorer-Neustart
sinnvoll.

Beim Debuggen sollte berücksichtigt werden:

- ein Absturz der DLL ist ein Absturz von explorer.exe,
- Stackalignment und Registererhaltung sind genauso kritisch wie Puffergrenzen,
- ListView-Nachrichten können während Refresh weitere Notifications erzeugen,
- Shell-/COM-Strings und Process-Heap-Strings besitzen unterschiedliche
  Freigabemechanismen,
- Fensterhandles sind nur so lange gültig wie die zugehörige Property Page.


39. Zusammenfassung
-------------------

ADSStreams 0.7.8 ist eine relativ kleine, aber technisch ungewöhnliche Explorer-
Erweiterung:

- vollständig in x64-FASM,
- klassischer COM-In-Process-Server,
- dynamische Property-Sheet-Page ohne Ressourcencompiler,
- native NTFS-Stream-Enumeration,
- begrenzte sichere Vorschau,
- vollständiger blockweiser Import/Export,
- eng validierter Delete-Pfad,
- direkte ADS-Kompatibilitätstests,
- inhaltsbasierte MZ-Warnung,
- dynamische DPI-/fontabhängige Fensterbreite,
- per-user regsvr32-Selbstregistrierung,
- keine CRT-Abhängigkeit.

Gerade die Kombination aus Explorer-Integration, manuell implementiertem COM,
NTFS-Stream-APIs und direkter Win64-ABI-Arbeit macht das Projekt aus technischer Sicht
interessant.

Ende der technischen Dokumentation.
